<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>aTrust 综合管理台</title>
    <style>
        body { font-family: "Microsoft YaHei"; background: #f7f9fb; margin: 0; padding: 20px; }
        h1 { text-align: center; color: #007bff; }
        section { background: #fff; margin: 20px auto; padding: 20px; border-radius: 8px; width: 900px;
            box-shadow: 0 2px 6px rgba(0,0,0,0.1); }
        h2 { border-left: 4px solid #007bff; padding-left: 8px; color: #333; }
        label { display: inline-block; width: 180px; margin: 4px 0; }
        input, select { width: 300px; padding: 6px; margin-bottom: 6px; border: 1px solid #ccc; border-radius: 5px; }
        button { background: #007bff; color: white; border: none; padding: 8px 16px; border-radius: 4px; cursor: pointer; }
        button:hover { background: #0056b3; }
        .output { background: #f4f4f4; padding: 10px; border-radius: 6px; margin-top: 10px; white-space: pre-wrap; font-size: 13px; }
    </style>
</head>
<body>
<h1>🚀 aTrust 综合管理台</h1>

<section>
    <h2>🧍 新增用户（支持角色选择）</h2>

    <form id="user-create">
        <label>directoryDomain：</label><input name="directoryDomain" value="local"><br>
        <label>name：</label><input name="name" placeholder="zhangsan"><br>
        <label>displayName：</label><input name="displayName" placeholder="张三"><br>
        <label>group fullPath：</label><input name="groupPath" value="/客服体系/专家中心/零信任专家组/开发组"><br>
        <label>externalId：</label><input name="externalId" value="81dc9bdb52d04dc2"><br>
        <label>password：</label><input name="password" value="user@1234"><br>
        <label>email：</label><input name="email" value="zhangsan@example.com"><br>
        <label>phone：</label><input name="phone" value="13800000000"><br>
        <label>description：</label><input name="description" value="通过OpenAPI创建"><br>

        <label>选择角色：</label>
        <select id="roleSelect">
            <option value="">（加载中...）</option>
        </select><br>

        <h3>🔍 角色推荐功能</h3>
        <label>想要访问的应用资源：</label><br>
        <textarea id="desiredResources" placeholder="输入应用名、域名、IP地址或IP段，每行一个或多个用逗号分隔\n例如：\n人事管理系统\n192.168.1.0/24\nhttps://www.cnki.net2\n192.168.1.100,example.com" rows="5" cols="50"></textarea><br>
        <button type="button" onclick="recommendRoles()">推荐角色</button>
        <div id="recommendation-output" class="output" style="margin-top: 10px;"></div>

        <h3>🆕 新建角色（当推荐角色不合适时）</h3>
        <label>新角色名称：</label><input id="newRoleName" placeholder="输入新角色名称"><br>
        <label>角色描述：</label><input id="newRoleDescription" placeholder="输入角色描述"><br>

        <h4>📋 Web应用必须参数</h4>
        <table border="1" style="border-collapse: collapse; width: 100%; margin: 10px 0;">
            <thead>
                <tr style="background-color: #f5f5f5;">
                    <th style="padding: 8px;">参数名称</th>
                    <th style="padding: 8px;">参数值</th>
                    <th style="padding: 8px;">说明</th>
                </tr>
            </thead>
            <tbody>
                <tr>
                    <td style="padding: 8px;">应用名称</td>
                    <td style="padding: 8px;"><input id="webAppName" placeholder="输入Web应用名称" style="width: 100%;"></td>
                    <td style="padding: 8px;">Web应用的名称</td>
                </tr>
                <tr>
                    <td style="padding: 8px;">应用分类ID</td>
                    <td style="padding: 8px;"><input id="webGroupId" placeholder="应用分类ID（可选）" style="width: 100%;"></td>
                    <td style="padding: 8px;">所属应用分类ID，与分类名称二选一</td>
                </tr>
                <tr>
                    <td style="padding: 8px;">应用分类名称</td>
                    <td style="padding: 8px;"><input id="webGroupName" value="默认分类" style="width: 100%;"></td>
                    <td style="padding: 8px;">所属应用分类名称，与分类ID二选一</td>
                </tr>
                <tr>
                    <td style="padding: 8px;">节点区域ID</td>
                    <td style="padding: 8px;"><input id="webNodeGroupId" value="162a5d3c-2266-4643-9c50-c5e619fa7e6e" style="width: 100%;"></td>
                    <td style="padding: 8px;">节点区域唯一标识</td>
                </tr>
                <tr>
                    <td style="padding: 8px;">后端应用服务器地址</td>
                    <td style="padding: 8px;"><input id="webAppAddress" placeholder="https://example.com" style="width: 100%;"></td>
                    <td style="padding: 8px;">后端应用服务器地址</td>
                </tr>
                <tr>
                    <td style="padding: 8px;">前端应用访问地址</td>
                    <td style="padding: 8px;"><input id="webAccessAddress" placeholder="https://webapp.example.com" style="width: 100%;"></td>
                    <td style="padding: 8px;">前端应用访问地址</td>
                </tr>
                <tr>
                    <td style="padding: 8px;">浏览器打开地址</td>
                    <td style="padding: 8px;"><input id="webAccessEntry" placeholder="https://webapp.example.com/" style="width: 100%;"></td>
                    <td style="padding: 8px;">浏览器打开地址</td>
                </tr>
                <tr>
                    <td style="padding: 8px;">隐藏应用</td>
                    <td style="padding: 8px;">
                        <select id="webHide" style="width: 100%;">
                            <option value="0">不隐藏</option>
                            <option value="1">隐藏</option>
                        </select>
                    </td>
                    <td style="padding: 8px;">在用户应用中心隐藏，0不隐藏，1隐藏</td>
                </tr>
                <tr>
                    <td style="padding: 8px;">启用依赖站点</td>
                    <td style="padding: 8px;">
                        <select id="webDependSitesEnable" style="width: 100%;">
                            <option value="0">禁用</option>
                            <option value="1">启用</option>
                        </select>
                    </td>
                    <td style="padding: 8px;">依赖站点功能需要配置泛域名的授信证书才能开启</td>
                </tr>
            </tbody>
        </table>

        <h4>📋 隧道应用必须参数</h4>
        <table border="1" style="border-collapse: collapse; width: 100%; margin: 10px 0;">
            <thead>
                <tr style="background-color: #f5f5f5;">
                    <th style="padding: 8px;">参数名称</th>
                    <th style="padding: 8px;">参数值</th>
                    <th style="padding: 8px;">说明</th>
                </tr>
            </thead>
            <tbody>
                <tr>
                    <td style="padding: 8px;">应用分类名称</td>
                    <td style="padding: 8px;"><input id="tunnelAppGroupName" value="默认分类" style="width: 100%;"></td>
                    <td style="padding: 8px;">所属应用分类名称</td>
                </tr>
                <tr>
                    <td style="padding: 8px;">节点区域ID</td>
                    <td style="padding: 8px;"><input id="tunnelNodeGroupId" value="162a5d3c-2266-4643-9c50-c5e619fa7e6e" style="width: 100%;"></td>
                    <td style="padding: 8px;">节点区域唯一标识</td>
                </tr>
                <tr>
                    <td style="padding: 8px;">隧道访问地址</td>
                    <td style="padding: 8px;"><input id="tunnelAccessAddress" value="https://tunnel.example.com" style="width: 100%;"></td>
                    <td style="padding: 8px;">隧道应用的访问入口地址</td>
                </tr>
                <tr>
                    <td style="padding: 8px;">隧道端口范围</td>
                    <td style="padding: 8px;"><input id="tunnelPortRange" value="0-65535" style="width: 100%;"></td>
                    <td style="padding: 8px;">隧道应用的端口范围</td>
                </tr>
                <tr>
                    <td style="padding: 8px;">协议类型</td>
                    <td style="padding: 8px;">
                        <select id="tunnelProtocol" style="width: 100%;">
                            <option value="tcp">TCP</option>
                            <option value="udp">UDP</option>
                            <option value="both">TCP+UDP</option>
                        </select>
                    </td>
                    <td style="padding: 8px;">隧道应用的协议类型</td>
                </tr>
            </tbody>
        </table>

        <button type="button" onclick="createNewRole()">新建角色并分配资源</button>
        <button type="button" onclick="saveAppParams()" style="background-color: #28a745;">保存参数</button>
        <button type="button" onclick="loadAppParams()" style="background-color: #17a2b8;">加载参数</button>
        <button type="button" onclick="resetAppParams()" style="background-color: #6c757d;">重置参数</button>
        <div id="new-role-output" class="output" style="margin-top: 10px;"></div>

        <button type="button" onclick="createUser()">新增用户并分配角色</button>
    </form>

    <div id="user-output" class="output"></div>
</section>

<script>
    const BASE_URL = "http://localhost:8080";

    /* 🔹 页面加载时加载角色下拉列表和应用参数 */
    window.addEventListener("DOMContentLoaded", async () => {
        // 1️⃣ 加载角色下拉列表
        const select = document.getElementById("roleSelect");
        try {
            console.log("🔍 开始加载角色列表...");
            console.log("🔍 请求URL:", BASE_URL + "/role/queryAll");
            console.log("🔍 请求参数:", { directoryDomain: "local", pageSize: 100, pageIndex: 1 });

            const resp = await fetch(BASE_URL + "/role/queryAll", {
                method: "POST",
                headers: { "Content-Type": "application/json" },
                body: JSON.stringify({ directoryDomain: "local", pageSize: 100, pageIndex: 1 })
            });

            // 检查HTTP响应状态
            if (!resp.ok) {
                const errorText = await resp.text();
                console.error("❌ HTTP错误响应:", errorText);
                throw new Error(`HTTP ${resp.status}: ${resp.statusText} - ${errorText}`);
            }

            const result = await resp.json();
            console.log("🔍 角色列表接口返回：", result);

            // 支持多种响应结构
            let list = [];
            if (result && result.data) {
                if (Array.isArray(result.data)) {
                    list = result.data;
                } else if (result.data.data && Array.isArray(result.data.data)) {
                    list = result.data.data;
                } else if (result.data.records && Array.isArray(result.data.records)) {
                    list = result.data.records;
                } else if (result.data.list && Array.isArray(result.data.list)) {
                    list = result.data.list;
                } else if (result.data.items && Array.isArray(result.data.items)) {
                    list = result.data.items;
                }
            }

            console.log("🔍 解析后的角色列表：", list);

            if (list.length > 0) {
                select.innerHTML = list.map(r => {
                    const roleName = r.name || r.roleName || r.displayName || '未知角色';
                    return `<option value="${roleName}">${roleName}</option>`;
                }).join("");
                console.log("✅ 角色列表加载成功，共" + list.length + "个角色");
            } else {
                select.innerHTML = `<option value="">（无可用角色）</option>`;
                console.warn("⚠️ 角色列表为空");
            }
        } catch (err) {
            select.innerHTML = `<option value="">加载角色失败</option>`;
            console.error("加载角色失败:", err);
        }

        // 2️⃣ 加载保存的应用参数
        loadAppParams();
    });


    /* 🔹 新增用户并分配角色 */
    async function createUser() {
        const f = Object.fromEntries(new FormData(document.getElementById("user-create")).entries());
        const selectedRole = document.getElementById("roleSelect").value;
        const output = document.getElementById("user-output");

        // 1️⃣ 创建用户
        const userBody = {
            directoryDomain: f.directoryDomain,
            name: f.name,
            group: { key: "fullPath", data: f.groupPath },
            externalId: f.externalId,
            inheritGroup: 1,
            password: f.password,
            pwdModel: "rsa",
            displayName: f.displayName,
            description: f.description,
            status: 1,
            phone: f.phone,
            email: f.email,
            expiredTime: "2026-12-31 23:59:59"
        };

        try {
            output.textContent = "⏳ 正在创建用户...";
            const res1 = await fetch(BASE_URL + "/user/create", {
                method: "POST", headers: { "Content-Type": "application/json" },
                body: JSON.stringify(userBody)
            });
            const data1 = await res1.json();
            if (data1.code !== "OK") {
                output.textContent = "❌ 用户创建失败：" + JSON.stringify(data1, null, 2);
                return;
            }

            // 2️⃣ 分配角色（如果选择了角色）
            if (selectedRole) {
                output.textContent = "✅ 用户创建成功，正在分配角色...";
                const assignBody = {
                    directoryDomain: f.directoryDomain,
                    name: f.name,
                    role: { op: "append", key: "name", data: [selectedRole] }
                };
                const res2 = await fetch(BASE_URL + "/user/assignRoleByName", {
                    method: "POST", headers: { "Content-Type": "application/json" },
                    body: JSON.stringify(assignBody)
                });
                const data2 = await res2.json();
                output.textContent = "✅ 完成：\n" + JSON.stringify({ createUser: data1, assignRole: data2 }, null, 2);
            } else {
                output.textContent = "✅ 用户创建成功（未分配角色）";
            }
        } catch (err) {
            output.textContent = "❌ 请求异常：" + err.message;
            console.error(err);
        }
    }

    /* 🔹 角色推荐功能 */
    async function recommendRoles() {
        const resourcesText = document.getElementById("desiredResources").value.trim();
        const output = document.getElementById("recommendation-output");

        if (!resourcesText) {
            output.textContent = "❌ 请输入想要访问的应用资源";
            return;
        }

        // 解析输入：支持多行和逗号分隔
        const queries = [];
        const lines = resourcesText.split('\n');
        for (const line of lines) {
            const items = line.split(',').map(item => item.trim()).filter(item => item);
            queries.push(...items);
        }

        if (queries.length === 0) {
            output.textContent = "❌ 没有有效的查询条件";
            return;
        }

        const body = {
            directoryDomain: "local",
            query: queries
        };

        try {
            output.textContent = "⏳ 正在推荐角色...";
            const response = await fetch(BASE_URL + "/api/role/recommend", {
                method: "POST",
                headers: { "Content-Type": "application/json" },
                body: JSON.stringify(body)
            });

            const result = await response.json();

            if (result.code === "OK" && result.data && result.data.length > 0) {
                let outputHtml = "✅ 推荐角色结果：\n\n";

                result.data.forEach(role => {
                    outputHtml += `📋 角色名称: ${role.roleName}\n`;
                    outputHtml += `📊 覆盖率: ${role.coverage}\n`;
                    outputHtml += `🔢 匹配数量: ${role.matched}\n`;
                    outputHtml += `📱 匹配应用: ${role.matchedAppNames.join(', ')}\n\n`;

                    outputHtml += "📋 匹配详情:\n";
                    role.grantedResources.forEach(resource => {
                        outputHtml += `  • ${resource.appName} (${resource.matchedType}: ${resource.matchedHost})\n`;
                    });
                    outputHtml += "\n";
                });

                output.textContent = outputHtml;

                // 自动填充角色选择下拉框
                const roleSelect = document.getElementById("roleSelect");
                const recommendedRoles = result.data.map(r => r.roleName);

                // 添加推荐的角色到下拉框
                recommendedRoles.forEach(roleName => {
                    if (!Array.from(roleSelect.options).some(opt => opt.value === roleName)) {
                        const option = document.createElement("option");
                        option.value = roleName;
                        option.textContent = roleName + " (推荐)";
                        roleSelect.appendChild(option);
                    }
                });

            } else {
                output.textContent = "❌ " + (result.msg || "未找到匹配的角色");
            }
        } catch (err) {
            output.textContent = "❌ 请求异常：" + err.message;
            console.error(err);
        }
    }

    /* 🔹 智能识别资源类型 */
    function identifyResourceType(resource) {
        if (resource.startsWith('http://') || resource.startsWith('https://')) {
            return 'DOMAIN';
        }
        if (resource.includes('/') && resource.match(/^(\d+\.){3}\d+\/\d+$/)) {
            return 'CIDR';
        }
        if (resource.match(/^(\d+\.){3}\d+$/)) {
            return 'IP';
        }
        if (resource.includes('.') && resource.match(/^[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/)) {
            return 'DOMAIN';
        }
        return 'APP_NAME';
    }

    /* 🔹 从URL中提取域名 */
    function extractDomainFromUrl(url) {
        if (url.startsWith('http://') || url.startsWith('https://')) {
            const domain = url.replace(/^https?:\/\//, '').split('/')[0];
            return domain;
        }
        return url;
    }

    /* 🔹 创建Web应用 */
    async function createWebApp(appName, domain) {
        // 使用传入的应用名称，确保创建和查询时名称一致
        const webAppName = appName;

        // 获取应用分类参数（groupId优先）
        const groupId = document.getElementById("webGroupId").value.trim();
        const groupName = document.getElementById("webGroupName").value.trim();

        // 获取地址参数，如果为空则使用域名自动生成
        const appAddress = document.getElementById("webAppAddress").value.trim() || `https://${domain}`;
        const accessAddress = document.getElementById("webAccessAddress").value.trim() || `https://${domain}`;
        const accessEntry = document.getElementById("webAccessEntry").value.trim() || `https://${domain}/`;

        // 构建ext扩展字段
        const ext = {
            hide: parseInt(document.getElementById("webHide").value) || 0
        };

        // 如果启用了依赖站点，添加dependSites配置
        const dependSitesEnable = parseInt(document.getElementById("webDependSitesEnable").value) || 0;
        if (dependSitesEnable === 1) {
            ext.dependSites = {
                enable: 1
            };
        }

        const webAppBody = {
            accessModel: "WEB",
            subModel: "WEB",
            name: webAppName,
            description: `Web应用 - ${domain}`,
            nodeGroupId: document.getElementById("webNodeGroupId").value,
            appAddress: appAddress,
            accessAddress: accessAddress,
            accessEntry: accessEntry,
            ext: ext
        };

        // 添加应用分类参数（groupId优先）
        if (groupId) {
            webAppBody.groupId = groupId;
        } else if (groupName) {
            webAppBody.groupName = groupName;
        }

        console.log("🔍 创建Web应用请求体:", webAppBody);

        const response = await fetch(BASE_URL + "/app/webapp/create", {
            method: "POST",
            headers: { "Content-Type": "application/json" },
            body: JSON.stringify(webAppBody)
        });

        // 检查HTTP响应状态
        if (!response.ok) {
            const errorText = await response.text();
            console.error("❌ HTTP错误响应:", errorText);
            throw new Error(`HTTP ${response.status}: ${response.statusText} - ${errorText}`);
        }

        const result = await response.json();
        console.log("🔍 创建Web应用响应:", result);

        return result;
    }

    /* 🔹 创建隧道应用 */
    async function createTunnelApp(appName, ipOrCidr) {
        const protocol = document.getElementById("tunnelProtocol").value;
        const addressList = [];

        // 根据协议类型创建地址列表
        if (protocol === "tcp" || protocol === "both") {
            addressList.push({
                protocol: "tcp",
                host: ipOrCidr,
                port: document.getElementById("tunnelPortRange").value
            });
        }
        if (protocol === "udp" || protocol === "both") {
            addressList.push({
                protocol: "udp",
                host: ipOrCidr,
                port: document.getElementById("tunnelPortRange").value
            });
        }

        const tunnelAppBody = {
            accessModel: "L3VPN",
            subModel: "L3VPN",
            name: appName,
            description: `隧道应用 - ${ipOrCidr}`,
            groupName: document.getElementById("tunnelAppGroupName").value,
            nodeGroupId: document.getElementById("tunnelNodeGroupId").value,
            accessAddress: document.getElementById("tunnelAccessAddress").value,
            addressList: addressList,
            ext: {}
        };

        const response = await fetch(BASE_URL + "/app/tunnelapp/create", {
            method: "POST",
            headers: { "Content-Type": "application/json" },
            body: JSON.stringify(tunnelAppBody)
        });

        return await response.json();
    }

    /* 🔹 保存应用参数到本地存储 */
    function saveAppParams() {
        const params = {
            // Web应用参数
            webAppName: document.getElementById("webAppName").value,
            webGroupId: document.getElementById("webGroupId").value,
            webGroupName: document.getElementById("webGroupName").value,
            webNodeGroupId: document.getElementById("webNodeGroupId").value,
            webAppAddress: document.getElementById("webAppAddress").value,
            webAccessAddress: document.getElementById("webAccessAddress").value,
            webAccessEntry: document.getElementById("webAccessEntry").value,
            webHide: document.getElementById("webHide").value,
            webDependSitesEnable: document.getElementById("webDependSitesEnable").value,

            // 隧道应用参数
            tunnelAppGroupName: document.getElementById("tunnelAppGroupName").value,
            tunnelNodeGroupId: document.getElementById("tunnelNodeGroupId").value,
            tunnelAccessAddress: document.getElementById("tunnelAccessAddress").value,
            tunnelPortRange: document.getElementById("tunnelPortRange").value,
            tunnelProtocol: document.getElementById("tunnelProtocol").value
        };

        localStorage.setItem('appParams', JSON.stringify(params));

        const output = document.getElementById("new-role-output");
        output.textContent = "✅ 应用参数已保存到本地存储";
    }

    /* 🔹 从本地存储加载应用参数 */
    function loadAppParams() {
        const savedParams = localStorage.getItem('appParams');
        if (savedParams) {
            const params = JSON.parse(savedParams);

            // Web应用参数
            document.getElementById("webAppName").value = params.webAppName || "";
            document.getElementById("webGroupId").value = params.webGroupId || "";
            document.getElementById("webGroupName").value = params.webGroupName || "默认分类";
            document.getElementById("webNodeGroupId").value = params.webNodeGroupId || "162a5d3c-2266-4643-9c50-c5e619fa7e6e";
            document.getElementById("webAppAddress").value = params.webAppAddress || "";
            document.getElementById("webAccessAddress").value = params.webAccessAddress || "";
            document.getElementById("webAccessEntry").value = params.webAccessEntry || "";
            document.getElementById("webHide").value = params.webHide || "0";
            document.getElementById("webDependSitesEnable").value = params.webDependSitesEnable || "0";

            // 隧道应用参数
            document.getElementById("tunnelAppGroupName").value = params.tunnelAppGroupName || "默认分类";
            document.getElementById("tunnelNodeGroupId").value = params.tunnelNodeGroupId || "162a5d3c-2266-4643-9c50-c5e619fa7e6e";
            document.getElementById("tunnelAccessAddress").value = params.tunnelAccessAddress || "https://tunnel.example.com";
            document.getElementById("tunnelPortRange").value = params.tunnelPortRange || "0-65535";
            document.getElementById("tunnelProtocol").value = params.tunnelProtocol || "tcp";

            const output = document.getElementById("new-role-output");
            output.textContent = "✅ 应用参数已从本地存储加载";
        } else {
            const output = document.getElementById("new-role-output");
            output.textContent = "ℹ️ 没有找到保存的应用参数，使用默认值";
        }
    }

    /* 🔹 重置应用参数到默认值 */
    function resetAppParams() {
        // Web应用参数
        document.getElementById("webAppName").value = "";
        document.getElementById("webGroupId").value = "";
        document.getElementById("webGroupName").value = "默认分类";
        document.getElementById("webNodeGroupId").value = "162a5d3c-2266-4643-9c50-c5e619fa7e6e";
        document.getElementById("webAppAddress").value = "";
        document.getElementById("webAccessAddress").value = "";
        document.getElementById("webAccessEntry").value = "";
        document.getElementById("webHide").value = "0";
        document.getElementById("webDependSitesEnable").value = "0";

        // 隧道应用参数
        document.getElementById("tunnelAppGroupName").value = "默认分类";
        document.getElementById("tunnelNodeGroupId").value = "162a5d3c-2266-4643-9c50-c5e619fa7e6e";
        document.getElementById("tunnelAccessAddress").value = "https://tunnel.example.com";
        document.getElementById("tunnelPortRange").value = "0-65535";
        document.getElementById("tunnelProtocol").value = "tcp";

        const output = document.getElementById("new-role-output");
        output.textContent = "✅ 应用参数已重置为默认值";
    }

    /* 🔹 新建角色并智能创建应用 */
    async function createNewRole() {
        const roleName = document.getElementById("newRoleName").value.trim();
        const roleDescription = document.getElementById("newRoleDescription").value.trim();
        const resourcesText = document.getElementById("desiredResources").value.trim();
        const output = document.getElementById("new-role-output");

        if (!roleName) {
            output.textContent = "❌ 请输入新角色名称";
            return;
        }

        if (!resourcesText) {
            output.textContent = "❌ 请先输入想要访问的应用资源";
            return;
        }

        try {
            output.textContent = "⏳ 正在创建新角色...";

            // 1️⃣ 创建新角色
            const createRoleBody = {
                directoryDomain: "local",
                name: roleName,
                description: roleDescription || "基于用户需求创建的自定义角色",
                externalId: "custom_" + Date.now()
            };

            const roleResponse = await fetch(BASE_URL + "/role/create", {
                method: "POST",
                headers: { "Content-Type": "application/json" },
                body: JSON.stringify(createRoleBody)
            });

            const roleResult = await roleResponse.json();

            if (roleResult.code !== "OK" ) {
                output.textContent = "❌ 创建角色失败：" + roleResult.msg;
                return;
            }

            output.textContent = "✅ 角色创建成功，正在创建应用并分配资源...\n";

            // 2️⃣ 解析资源并智能创建应用
            const queries = [];
            const lines = resourcesText.split('\n');
            for (const line of lines) {
                const items = line.split(',').map(item => item.trim()).filter(item => item);
                queries.push(...items);
            }

            let createdApps = [];
            let createdCount = 0;
            let assignedCount = 0;

            for (const resource of queries) {
                const resourceType = identifyResourceType(resource);
                let appName = resource;
                let appResult = null;

                try {
                    // 根据资源类型创建对应的应用
                    if (resourceType === 'DOMAIN') {
                        const domain = extractDomainFromUrl(resource);
                        // 使用用户输入的应用名称，如果没有则使用域名
                        const userAppName = document.getElementById("webAppName").value.trim();
                        appName = userAppName || `Web应用 - ${domain}`;
                        output.textContent += `🌐 正在创建Web应用: ${appName}\n`;
                        appResult = await createWebApp(appName, domain);
                    } else if (resourceType === 'IP' || resourceType === 'CIDR') {
                        // 使用用户输入的应用名称，如果没有则使用IP/CIDR
                        const userAppName = document.getElementById("webAppName").value.trim();
                        appName = userAppName || `隧道应用 - ${resource}`;
                        output.textContent += `🔗 正在创建隧道应用: ${appName}\n`;
                        appResult = await createTunnelApp(appName, resource);
                    } else {
                        // 应用名称类型，使用用户输入的应用名称或原名称
                        const userAppName = document.getElementById("webAppName").value.trim();
                        appName = userAppName || resource;
                        output.textContent += `📱 正在创建应用: ${appName}\n`;
                        // 默认创建为Web应用
                        appResult = await createWebApp(appName, appName);
                    }

                    console.log("🔍 应用创建结果:", appResult);
                    if (appResult && (appResult.code === "OK" || appResult.code === 0 || appResult.code === "0")) {
                        createdApps.push(appName);
                        createdCount++;
                        output.textContent += `✅ 创建应用成功: ${appName}\n`;

                        // 直接使用创建时使用的应用名称进行权限分配
                        const actualAppName = appName;
                        output.textContent += `🔍 使用应用名称进行权限分配: ${actualAppName}\n`;

                        // 为创建的应用分配权限到角色（只分配应用，不分配应用分类）
                        const assignBody = {
                            directoryDomain: "local",
                            name: roleName,
                            resource: {
                                op: "append",
                                key: "name",
                                data: [
                                    {
                                        data: actualAppName.trim(),
                                        effectiveTime: "2025-01-01 00:00:00",
                                        expireTime: "2026-12-31 23:59:59"
                                    }
                                ]
                            }
                        };

                        console.log("🔍 权限分配请求体:", assignBody);
                        console.log("🔍 权限分配请求URL:", BASE_URL + "/role/assignResourceByName");
                        const assignResponse = await fetch(BASE_URL + "/role/assignResourceByName", {
                            method: "POST",
                            headers: { "Content-Type": "application/json" },
                            body: JSON.stringify(assignBody)
                        });

                        const assignResult = await assignResponse.json();
                        console.log("🔍 权限分配响应:", assignResult);

                        // 检查HTTP响应状态
                        if (!assignResponse.ok) {
                            output.textContent += `❌ 分配权限失败: ${appName} - HTTP ${assignResponse.status}: ${assignResponse.statusText}\n`;
                            continue;
                        }

                        // 支持多种成功代码格式
                        if (assignResult.code === "OK" || assignResult.code === 0 || assignResult.code === "0" || assignResult.code === 200) {
                            assignedCount++;
                            output.textContent += `✅ 分配权限成功: ${appName}\n`;
                        } else {
                            output.textContent += `❌ 分配权限失败: ${appName} - ${assignResult?.msg || assignResult?.message || '未知错误'} (code: ${assignResult?.code})\n`;
                        }
                    } else {
                        output.textContent += `❌ 创建应用失败: ${appName} - ${appResult?.msg || '未知错误'}\n`;
                    }
                } catch (err) {
                    output.textContent += `❌ 处理资源失败: ${resource} - ${err.message}\n`;
                    console.warn("处理资源失败:", resource, err);
                }
            }

            // 3️⃣ 更新角色选择下拉框
            const roleSelect = document.getElementById("roleSelect");
            const option = document.createElement("option");
            option.value = roleName;
            option.textContent = roleName + " (新建)";
            roleSelect.appendChild(option);
            roleSelect.value = roleName;

            output.textContent += `\n✅ 新建角色成功！\n` +
                               `📋 角色名称: ${roleName}\n` +
                               `📊 创建应用: ${createdCount}/${queries.length} 个应用\n` +
                               `🔗 分配权限: ${assignedCount}/${createdCount} 个应用\n` +
                               `📱 创建的应用: ${createdApps.join(', ')}\n` +
                               `💡 该角色已自动选中，可直接创建用户`;

        } catch (err) {
            output.textContent = "❌ 请求异常：" + err.message;
            console.error(err);
        }
    }
</script>

<!-- ========== 角色模块 ========== -->
<section>
    <h2>🔐 角色管理</h2>

    <h3>新增角色</h3>
    <form id="role-create">
        <label>directoryDomain：</label><input name="directoryDomain" value="local"><br>
        <label>name：</label><input name="name" value="role1"><br>
        <label>description：</label><input name="description" value="role created by openAPI"><br>
        <label>externalId：</label><input name="externalId" value="1"><br>
        <button type="button" onclick="createRole()">新增角色</button>
    </form>

    <h3>查询角色详情</h3>
    <form id="role-query">
        <label>directoryDomain：</label><input name="directoryDomain" value="local">
        <label>name：</label><input name="name" value="role1">
        <button type="button" onclick="queryRole()">查询</button>
    </form>

    <h3>角色列表查询</h3>
    <form id="role-list">
        <label>directoryDomain：</label><input name="directoryDomain" value="local">
        <button type="button" onclick="queryRoleList()">查询列表</button>
    </form>

    <h3>角色授权应用</h3>
    <form id="role-assign">
        <label>directoryDomain：</label><input name="directoryDomain" value="local"><br>
        <label>name：</label><input name="name" value="role1"><br>
        <label>应用名：</label><input name="appName" value="[RDP]张三的windows桌面"><br>
        <label>分类名：</label><input name="groupName" value="默认分类"><br>
        <button type="button" onclick="assignAppToRole()">角色授权应用</button>
    </form>

    <div id="role-output" class="output"></div>
</section>

<!-- ========== 应用模块 ========== -->
<section>
    <h2>🌐 应用管理</h2>

    <h3>新增应用</h3>
    <form id="app-create">
        <label>name：</label><input name="name" value="知网"><br>
        <label>description：</label><input name="description" value="CNKI Web 应用"><br>
        <label>groupName：</label><input name="groupName" value="默认分类"><br>
        <label>nodeGroupId：</label><input name="nodeGroupId" value="162a5d3c-2266-4643-9c50-c5e619fa7e6e"><br>
        <label>appAddress：</label><input name="appAddress" value="https://www.cnki.net"><br>
        <label>accessAddress：</label><input name="accessAddress" value="https://webapp.cnki.net"><br>
        <label>accessEntry：</label><input name="accessEntry" value="https://webapp.cnki.net/"><br>
        <button type="button" onclick="createApp()">新增应用</button>
    </form>

    <h3>查询应用详情</h3>
    <form id="app-query">
        <label>name：</label><input name="name" value="知网">
        <button type="button" onclick="queryApp()">查询</button>
    </form>

    <h3>查询应用列表</h3>
    <form id="app-list">
        <label>groupName：</label><input name="groupName" value="默认分类"><br>
        <label>filter：</label><input name="filter" value="name"><br>
        <label>pageSize：</label><input name="pageSize" value="10"><br>
        <label>pageIndex：</label><input name="pageIndex" value="1"><br>
        <button type="button" onclick="queryAppList()">查询列表</button>
    </form>

    <div id="app-output" class="output"></div>
</section>

<script>
    const BASE_URL = "http://localhost:8080";

    /* 工具函数 */
    function showOutput(id, text) {
        document.getElementById(id).textContent = JSON.stringify(text, null, 2);
    }

    /* ========= 用户 ========= */
    async function createUser() {
        const form = Object.fromEntries(new FormData(document.getElementById("user-create")).entries());
        const body = {
            directoryDomain: form.directoryDomain,
            name: form.name,
            group: { key: "fullPath", data: form.groupPath },
            externalId: form.externalId,
            inheritGroup: 1,
            password: form.password,
            pwdModel: "rsa",
            displayName: form.displayName,
            description: form.description,
            status: 1,
            phone: form.phone,
            email: form.email,
            expiredTime: "2026-12-31 23:59:59"
        };
        const res = await fetch(BASE_URL + "/user/create", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(body) });
        showOutput("user-output", await res.json());
    }

    async function queryUser() {
        const f = Object.fromEntries(new FormData(document.getElementById("user-query")).entries());
        const res = await fetch(`${BASE_URL}/user/queryByName?directoryDomain=${f.directoryDomain}&name=${f.name}`);
        showOutput("user-output", await res.json());
    }

    async function queryUserList() {
        const f = Object.fromEntries(new FormData(document.getElementById("user-list")).entries());
        const res = await fetch(BASE_URL + "/user/queryAll", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(f) });
        showOutput("user-output", await res.json());
    }

    /* ========= 角色 ========= */
    async function createRole() {
        const f = Object.fromEntries(new FormData(document.getElementById("role-create")).entries());
        const res = await fetch(BASE_URL + "/role/create", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(f) });
        showOutput("role-output", await res.json());
    }

    async function queryRole() {
        const f = Object.fromEntries(new FormData(document.getElementById("role-query")).entries());
        const res = await fetch(`${BASE_URL}/role/queryByName?directoryDomain=${f.directoryDomain}&name=${f.name}`);
        showOutput("role-output", await res.json());
    }

    async function queryRoleList() {
        const f = Object.fromEntries(new FormData(document.getElementById("role-list")).entries());
        const res = await fetch(BASE_URL + "/role/queryAll", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(f) });
        showOutput("role-output", await res.json());
    }

    async function assignAppToRole() {
        const f = Object.fromEntries(new FormData(document.getElementById("role-assign")).entries());
        const body = {
            directoryDomain: f.directoryDomain,
            name: f.name,
            resource: {
                op: "append",
                key: "name",
                data: [{ data: f.appName, effectiveTime: "2025-10-23 09:00:00", expireTime: "2026-10-23 09:00:00" }]
            },
            resourceGroup: {
                op: "append",
                key: "name",
                data: [{ data: f.groupName, effectiveTime: "2025-10-23 09:00:00", expireTime: "2026-10-23 09:00:00" }]
            }
        };
        const res = await fetch(BASE_URL + "/role/assignResourceByName", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(body) });
        showOutput("role-output", await res.json());
    }

    /* ========= 应用 ========= */
    async function createApp() {
        const f = Object.fromEntries(new FormData(document.getElementById("app-create")).entries());
        const body = {
            name: f.name,
            description: f.description,
            groupName: f.groupName,
            nodeGroupId: f.nodeGroupId,
            appAddress: f.appAddress,
            accessAddress: f.accessAddress,
            accessEntry: f.accessEntry,
            trustedCertName: "内置web应用证书（国际密码标准）",
            ext: { hide: 0 },
            applyForInfo: {
                content: "您当前没有访问该应用的权限，请联系管理员",
                accessReason: ["我因岗位职责需要访问", "我因组织结构需要访问"]
            }
        };
        const res = await fetch(BASE_URL + "/app/webapp/create", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(body) });
        showOutput("app-output", await res.json());
    }

    async function queryApp() {
        const f = Object.fromEntries(new FormData(document.getElementById("app-query")).entries());
        const res = await fetch(`${BASE_URL}/app/queryByName?name=${encodeURIComponent(f.name)}`);
        showOutput("app-output", await res.json());
    }

    async function queryAppList() {
        const f = Object.fromEntries(new FormData(document.getElementById("app-list")).entries());
        const res = await fetch(BASE_URL + "/app/queryAll", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(f) });
        showOutput("app-output", await res.json());
    }
</script>
</body>
</html>
